from __future__ import division

import math
import random

from concepts import *

from .effect import Effects
from .process import CallbackProcess
from .storage import Storage
from .terrain import *


class Building(Object):
    name        =_ (None)
    description =_ (None)
    
    size         =_ (1)
    cost         =_ (0)
    staff        =_ (0)
    color        =_ (None)
    terrain      =_ (set([FlatTerrain]))
    requires     =_ (None)
    stores       =_ (None)
    storage_unit =_ (100)
    detail       =_ (None)
    
    is_passable =_ (False)
    is_walkable =_ (False)
    is_road     =_ (False)
    is_eternal  =_ (False)
    
    city      = Attribute()
    logistics = city._.logistics
    
    position = Attribute(writable = True)
    workers  = Attribute(initial = 0, writable = True)
    x        = position._.x
    y        = position._.y
    
    virtual = Attribute(initial = False)
    
    storage        = Attribute()
    create         = storage._.add_goods
    consume        = storage._.remove_goods
    load_from      = storage._.load_from
    unload_into    = storage._.unload_into
    transfer_to    = storage._.transfer_to
    transfer_from  = storage._.transfer_from
    number_left_of = storage._.number_of
    all_of         = storage._.all
    has_some       = storage._.has
    num_storable   = storage._.free_storage_for
    
    effects       = Attribute()
    effect        = effects._.effect
    has_effect    = effects._.has_effect
    add_effect    = effects._.add_effect
    set_effect    = effects._.set_effect
    remove_effect = effects._.remove_effect
    
    processes = ListAttribute()
    
    def setup(self):
        if self.virtual:
            return
        
        self._effects = Effects(self)
        
        if self.stores is not None:
            if hasattr(self.stores, '__iter__'):
                self._storage = Storage(list(iter(self.stores)), unit = self.storage_unit)
            else:
                self._storage = Storage((self.stores,),          unit = self.storage_unit)
        
        self.city.jobs += self.staff
        
        self.logistics.add_building(self)
        self.construction()
    
    def realize(self):
        if not self.virtual:
            return
        
        self.city.realize(self)
        self._virtual = False
        self.setup()
    
    @property
    def calendar(self):
        return self._city._calendar
    
    @property
    def type(self):
        return self.__class__
    
    @property
    def has_storage(self):
        return self.storage is not None
    
    @property
    def tile(self):
        return self.city.map.at(self.position)
    
    @property
    def tiles(self):
        return [self.city.map.at(position)
                for position in self.area.positions]
    
    @property
    def area(self):
        return Area(self.position, self.size, self.size)
    
    @property
    def exists(self):
        return self._position is not None

    @property
    def is_fully_staffed(self):
        return self.workers >= self.staff
    
    @property
    def staffing_level(self):
        return self.workers / self.staff
    
    @property
    def efficiency(self):
        if self.staff == 0:
            return None
        else:
            return self.staffing_level ** 2
    
    @property
    def access_point(self):
        tiles = list(self.area.adjacent_with(lambda position: self.city.map.at(position).is_passable))
        
        if tiles:
            return random.choice(tiles)
        else:
            return None
    
    @property
    def walker_access_point(self):
        tiles = list(self.area.adjacent_with(lambda position: self.city.map.at(position).is_walkable))
        
        if tiles:
            return random.choice(tiles)
        else:
            return None
    
    @property
    def spawn_point(self):
        return self.access_point
    
    def spawn(self, type, position = None, **attributes):
        if position is None:
            spawn_point = self.spawn_point
        else:
            spawn_point = position
        
        if 'home' not in attributes:
            attributes['home'] = self
        
        if spawn_point is None:
            return None
        else:
            return self.city.spawn(type, spawn_point, **attributes)
    
    @property
    def supply(self):
        return ()
    
    @property
    def demand(self):
        return ()
    
    def number_needed_of(self, good):
        return self.storage.free_storage_for(good)
    
    def urgency_of(self, good):
        return 1
    
    def payday(self):
        self.city.money -= self.workers * self.city.wage
    
    def process(self, callback, speed):
        self._processes.append(BuildingProcess(speed, callback, self))
    
    def on_tick(self):
        pass
    
    def on_day(self, day, month, year):
        self.effects.day(day, month, year)
        
        if self.workers < self.staff and self.city.workers >= 1:
            transfer = min(self.staff - self.workers, self.city.workers)
            
            self.workers       += transfer
            self.city.employed += transfer
            self.city.workers  -= transfer
        
        if (self.workers > 0 and self.city.workers < 0) or self.workers > self.staff:
            transfer = min(self.workers, max(self.workers - self.staff, -max(self.city.workers, 0)))
            
            self.city.workers  += transfer
            self.city.employed -= transfer
            self.workers       -= transfer
        
        for process in self.processes:
            process.advance()
        
        self.day(day, month, year)
    
    def day(self, day, month, year):
        pass
    
    def destroy(self, update_city = True):
        if update_city:
            self.city.remove(self, update_object = False)
        
        self.city.jobs     -= self.staff
        self.city.employed -= self.workers
        self.city.workers  += self.workers
        self.workers        = 0
        
        self.logistics.remove_building(self)
        self.destruction()

    def construction(self):
        pass
    
    def destruction(self):
        pass
    
    @property
    def state(self):
        return {}
        
    def __repr__(self):
        return '<%s @ %s>' % (self.__class__.__name__, self.area)


class BuildingProcess(CallbackProcess):
    building = Attribute()
    
    @property
    def modifier(self):
        return (self.building.staffing_level ** 2 *
                self.building.calendar.ticks_per_day)


class StaffedBuilding(Building):
    profession =_ (None)
    delay      =_ (5)
    agents     =_ (1)
    
    individuals = ListAttribute()
    countdown   = Attribute(initial  = 0, writable = True)
    
    def setup(self):
        super(StaffedBuilding, self).setup()
        
        self.countdown = self.delay ** 0.5
    
    @property
    def ready(self):
        return True
    
    def day(self, day, month, year):
        self._individuals = [individual for individual in self.individuals
                             if individual.exists]
        
        if not self.ready or len(self.individuals) >= self.agents:
            return

        if self.countdown <= 0:
            self.countdown = self.delay
            self.create_individual()
        else:
            self.countdown -= self.staffing_level ** 2
    
    def create_individual(self):
        if self.spawn_point is None:
            return
        
        self._individuals.append(self.spawn(self.profession,
                                            **self.walker_attributes))
    
    @property
    def walker_attributes(self):
        return {}
    
    @property
    def spawn_point(self):
        return self.walker_access_point
